// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/* Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * VISS_WB_DMA driver code for LomboTech
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/bug.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/platform_device.h>

#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>

#include "media-dev.h"
#include "viss-wb-dma.h"
#include "viss.h"
#include "csp.h"

struct viss_wb_dma *g_wb_dma;

/* data path corresponds to dma */
static u16 dp_to_dma[] = {
	0x8003,
	0x400c,
	0x2030,
	0x10c0,
	0x0b00,
	0x0c00,
	0x3000,
	0xc000,
};

/* obtain dp structure through dp_id
 *@dp_id: data path id
 *return: data path data structure address
 */
static struct dp_data *wb_dma_get_dp_data(const u32 dp_id)
{
	struct viss_wb_dma *wb_dma = g_wb_dma;
	struct dp_data *dp_data;

	PRT_TRACE_BEGIN("\n");

	list_for_each_entry(dp_data, &wb_dma->head, node)
		if (dp_id == dp_data->dp_id)
			return dp_data;

	PRT_TRACE_END("\n");

	return NULL;
}

/* get the number of data path in use */
static u32 wb_dma_get_dp_used_num(void)
{
	struct viss_wb_dma *wb_dma = g_wb_dma;
	struct list_head *list;
	int count = 0;

	PRT_TRACE_BEGIN("\n");

	list_for_each(list, &wb_dma->head)
		count++;

	PRT_TRACE_END("\n");

	return count;
}

/*check if data path is idle
 *@dp_id: data path id
 *return:
 *<1:data path in use
 *1：through
 *2: simi plane
 *3: plane
 */
static int wb_dma_get_idle_dma(u32 dp_id)
{
	u16 flag = 0;
	int count = 0;
	struct viss_wb_dma *wb_dma = g_wb_dma;
	struct dp_data *dp_data = wb_dma_get_dp_data(dp_id);

	PRT_TRACE_BEGIN("\n");

	if (dp_data != NULL) {
		PRT_INFO("%d dp used\n", dp_id);
		return -1;
	}

	flag = ~wb_dma->dma_flags & dp_to_dma[dp_id];
	PRT_DBG("%d dp flags:%04x\n", dp_id, flag);

	flag = flag & ((1 << MAX_DMA) - 1);

	while (flag != 0) {
		flag = flag & (flag - 1);
		count++;
	}
	PRT_DBG("%d dp idle plane: %d\n", dp_id, count);

	PRT_TRACE_END("\n");

	return count;
}

/*check if data path is used
 *@dp_id: data path id
 *return:
 *<1:data path not in use
 *1：through
 *2: simi plane
 *3: plane
 */
static int wb_dma_get_busy_dma(u32 dp_id)
{
	u16 flag = 0;
	int count = 0;
	struct viss_wb_dma *wb_dma = g_wb_dma;
	struct dp_data *dp_data = wb_dma_get_dp_data(dp_id);

	PRT_TRACE_BEGIN("\n");

	if (dp_data == NULL) {
		PRT_ERR("%d not used\n", dp_id);
		return -1;
	}

	flag = wb_dma->dma_flags & dp_to_dma[dp_id];
	flag = flag & ((1 << MAX_DMA) - 1);

	PRT_DBG("%d dp flag:%04x\n", dp_id, flag);
	while (flag != 0) {
		flag = flag & (flag - 1);
		count++;
	}
	PRT_DBG("%d dp busy plane: %d\n", dp_id, count);

	PRT_TRACE_END("\n");

	return count;
}

/*set plane dmas of dp_id to 1
 *@dp_id: data path id
 *@plane: number of planes occupied by data format
 *retrun:
 *0: success
 *<0: fail
 */
static int wb_dma_set_dma_busy(u32 dp_id, u32 plane)
{
	u16 dp_dma = dp_to_dma[dp_id];
	struct viss_wb_dma *wb_dma = g_wb_dma;
	u32 dma_id = 0;
	u16 dma_flags = 0;

	PRT_TRACE_BEGIN("\n");

	for (dma_id = 0; dma_id < MAX_DMA; dma_id++) {
		if (!!(0x0001 & dp_dma) && plane) {
			plane--;
			dma_flags |= (1 << dma_id);
		}

		dp_dma = dp_dma >> 1;
	}
	PRT_DBG("before flags: %04x\n", wb_dma->dma_flags);
	if (!(wb_dma->dma_flags & dma_flags)) {
		wb_dma->dma_flags = wb_dma->dma_flags | dma_flags;
		PRT_DBG("after flags: %04x\n", wb_dma->dma_flags);
		return 0;
	}

	PRT_ERR("DP %d,busy\n", dp_id);

	return -1;
}

/*set plane dmas of dp_id to 0
 *@dp_id: data path id
 *@plane: number of planes occupied by data format
 *return:
 *0  success
 *<0  fail
 */
static int wb_dma_set_dma_idle(u32 dp_id, u32 plane)
{
	u16 dp_dma = dp_to_dma[dp_id];
	struct viss_wb_dma *wb_dma = g_wb_dma;
	u32 dma_id = 0;
	u16 dma_flags = 0;

	PRT_TRACE_BEGIN("\n");

	for (dma_id = 0; dma_id < MAX_DMA; dma_id++) {
		if (!!(0x0001 & dp_dma) && plane) {
			plane--;
			dma_flags |= (1 << dma_id);
		}

		dp_dma = dp_dma >> 1;
	}
	PRT_DBG("before flags: %04x\n", wb_dma->dma_flags);
	if (!((~wb_dma->dma_flags) & dma_flags)) {
		dma_flags = ~dma_flags;
		wb_dma->dma_flags = wb_dma->dma_flags & dma_flags;
		PRT_DBG("after flags: %04x\n", wb_dma->dma_flags);
		return 0;
	}

	PRT_ERR("DP %d,idle\n", dp_id);
	return -1;
}

/*apply of dp, register the interrupt function to dp data,
 *and occupy plane dma
 *@wb_dma_irq: point to the module interrupt function using the dp
 *@plane: number of planes occupied by data format
 *@priv: point to the module structure using the dp
 *return:0~7 dp_id applied to
 *        -1 no dp available or insufficient dma
 */
int wb_dma_req_dp(int (*wb_dma_irq)(void *data, u32 intsrc, u32 dp_id),
		u32 plane, void *priv)
{
	int ret = -1;
	u32 dp_id;
	struct viss_wb_dma *wb_dma = g_wb_dma;
	struct dp_data *dp_data;
	unsigned long flags;

	if (plane == 0) {
		PRT_WARN("plane:%d\n", plane);
		plane = 1;
	}

	PRT_TRACE_BEGIN("\n");

	spin_lock_irqsave(&wb_dma->slock, flags);
	for (dp_id = 0; dp_id < MAX_DATA_PATH; dp_id++) {

		ret = wb_dma_get_idle_dma(dp_id);
		PRT_DBG("get dp %d idle plane %d\n", dp_id, ret);

		if ((ret > 0) && (ret >= plane)) {
			ret = wb_dma_set_dma_busy(dp_id, plane);
			if (ret < 0)
				continue;
			else
				break;
		}

		ret = -1;
		continue;
	}
	spin_unlock_irqrestore(&wb_dma->slock, flags);

	if (ret >= 0) {
		PRT_DBG("used dp %d, plane %d\n", dp_id, plane);

		dp_data = devm_kzalloc(&wb_dma->pdev->dev,
			sizeof(*dp_data), GFP_KERNEL);

		dp_data->plane = plane;
		dp_data->priv = priv;
		dp_data->wb_dma_irq = wb_dma_irq;
		dp_data->dp_id = dp_id;

		spin_lock_irqsave(&wb_dma->slock, flags);
		list_add_tail(&dp_data->node, &wb_dma->head);
		spin_unlock_irqrestore(&wb_dma->slock, flags);

		ret = dp_id;

	} else
		PRT_ERR("req err %d\n", dp_id);

	return ret;
}

/* unreq dp */
int wb_dma_unreq_dp(u32 dp_id, u32 plane)
{
	int ret = -1;
	struct viss_wb_dma *wb_dma = g_wb_dma;
	struct dp_data *dp_data = wb_dma_get_dp_data(dp_id);
	unsigned long flags;

	PRT_TRACE_BEGIN("\n");

	if (dp_data == NULL) {
		PRT_ERR("not used dp %d\n", dp_id);
		return ret;
	}

	if (plane == 0) {
		PRT_WARN("plane:%d\n", plane);
		plane = 1;
	}

	spin_lock_irqsave(&wb_dma->slock, flags);

	ret = wb_dma_get_busy_dma(dp_id);

	if ((ret > 0) && (ret >= plane)) {
		ret = wb_dma_set_dma_idle(dp_id, plane);
		if (ret < 0)
			goto out;

		dp_data = wb_dma_get_dp_data(dp_id);
		if (dp_data == NULL)
			goto out;

		dp_data->plane = 0;
		dp_data->priv = NULL;
		dp_data->wb_dma_irq = NULL;
		dp_data->dp_id = 0xff;

		list_del(&dp_data->node);

		devm_kfree(&wb_dma->pdev->dev, dp_data);
		spin_unlock_irqrestore(&wb_dma->slock, flags);
		PRT_TRACE_END("\n");

		return 0;
	}
out:
	spin_unlock_irqrestore(&wb_dma->slock, flags);

	PRT_ERR("unreq err %d\n", dp_id);
	return ret;
}

/* wb_dma interrupt function */
static irqreturn_t wb_dma_irq_handler(int irq, void *priv)
{
	struct viss_wb_dma *wb_dma = priv;
	unsigned long flags;
	u32 dp_id;
	u32 intsrc;
	int ret = 0;
	struct dp_data *dp_data;

	PRT_TRACE_BEGIN("\n");

	spin_lock_irqsave(&wb_dma->slock, flags);

	list_for_each_entry(dp_data, &wb_dma->head, node) {
		dp_id = dp_data->dp_id;
		intsrc = csp_wb_dma_get_int_pd(dp_id);

		if (intsrc) {
			csp_wb_dma_clear_pd(dp_id, WB_DMA_INT_ALL_MASK);
			if ((dp_data->priv != NULL) &&
				(dp_data->wb_dma_irq != NULL))
				dp_data->wb_dma_irq(dp_data->priv,
						intsrc, dp_id);

			ret++;
		}
	}

	spin_unlock_irqrestore(&wb_dma->slock, flags);
	if (ret == 0) {
		PRT_ERR("IRQ_NONE\n");
		return IRQ_NONE;
	}

	PRT_TRACE_END("\n");

	return IRQ_HANDLED;
}

static ssize_t wb_dma_reparse_show(struct device *dev,
				 struct device_attribute *attr, char *buf)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct viss_wb_dma *wb_dma = platform_get_drvdata(pdev);
	struct dp_data *dp_data;
	unsigned long flags;
	int count = 0;
	int i = 0;

	spin_lock_irqsave(&wb_dma->slock, flags);

	count += sprintf(buf + count, "----------------------\n");

	count += sprintf(buf + count, "wb dma flags:%04x\n", wb_dma->dma_flags);

	i = wb_dma_get_dp_used_num();

	count += sprintf(buf + count, "dp used count:%d\n",
				i);
	count += sprintf(buf + count, "dp_id:");

	list_for_each_entry(dp_data, &wb_dma->head, node)
		count += sprintf(buf + count, " %d ", dp_data->dp_id);

	count += sprintf(buf + count, "\ndp plane num:");
	list_for_each_entry(dp_data, &wb_dma->head, node)
		count += sprintf(buf + count, " %d ", dp_data->plane);

	count += sprintf(buf + count, "\n");
	count += sprintf(buf + count, "----------------------\n");

	spin_unlock_irqrestore(&wb_dma->slock, flags);

	return count;
}

static ssize_t wb_dma_reparse_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	unsigned int plane = 0;
	unsigned int dp_id = 0;
	int ret = -1;
	unsigned int flags;

	if (kstrtouint(buf, 0, &flags)) {
		PRT_ERR("invalid parameter\n");
		return count;
	}

	if ((flags > 100) || (flags < 0)) {
		PRT_ERR("invalid parameter\n");
		return count;
	}
	if (flags > 10) {
		dp_id = flags / 10;
		plane = flags % 10;
		if ((plane == 0) || (plane > 3) || (dp_id > 9)) {
			PRT_ERR("invalid parameter\n");
			return count;
		}

		ret = wb_dma_unreq_dp(dp_id - 1, plane);
		if (ret < 0)
			PRT_ERR("unreq err\n");
	} else {
		if (flags > 3) {
			PRT_ERR("invalid parameter\n");
			return count;
		}

		ret = wb_dma_req_dp(NULL, flags, NULL);
		if (ret < 0)
			PRT_ERR("req err\n");
	}

	return count;
}

static DEVICE_ATTR_RW(wb_dma_reparse);

void wb_dma_set_dp_addr(u32 dp_id, struct viss_addr *paddr,
					u32 plane)
{
	PRT_TRACE_BEGIN("\n");

	if (plane == 0) {
		PRT_WARN("plane:%d\n", plane);
		plane = 1;
	}

	switch (plane) {
#if !defined(CONFIG_ARCH_LOMBO_N7V5) && !defined(CONFIG_ARCH_LOMBO_N5V1)
	case 3:
		csp_wb_dma_cmpt2_addr(dp_id, paddr->cr);
#endif
	case 2:
		csp_wb_dma_cmpt1_addr(dp_id, paddr->cb);
	case 1:
		csp_wb_dma_cmpt0_addr(dp_id, paddr->y);
		break;
	default:
		PRT_ERR("not %d plane\n", plane);
	}
}

static int wb_dma_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct viss_wb_dma *wb_dma;
	int ret = 0;
	struct resource *res;

	PRT_TRACE_BEGIN("\n");

	wb_dma = devm_kzalloc(dev, sizeof(*wb_dma), GFP_KERNEL);
	if (!wb_dma) {
		PRT_ERR("create struct wb_dma failed!\n");
		return -ENOMEM;
	}
	g_wb_dma = wb_dma;

	wb_dma->pdev = pdev;

	init_waitqueue_head(&wb_dma->irq_queue);
	spin_lock_init(&wb_dma->slock);
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	wb_dma->regs = devm_ioremap_resource(dev, res);
	if (IS_ERR(wb_dma->regs))
		return PTR_ERR(wb_dma->regs);

	csp_wb_dma_set_register_base(wb_dma->regs);

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (res == NULL) {
		dev_err(dev, "Failed to get IRQ resource\n");
		return -ENXIO;
	}

	csp_wb_dma_set_register_base(wb_dma->regs);

	ret = devm_request_irq(dev, res->start, wb_dma_irq_handler,
					0, dev_name(dev), wb_dma);
	if (ret) {
		dev_err(dev, "Failed to install irq (%d)\n", ret);
		return -ENXIO;
	}

	ret = device_create_file(&pdev->dev, &dev_attr_wb_dma_reparse);
	if (ret)
		PRT_ERR("device_create_file failed (%d)\n", ret);

	INIT_LIST_HEAD(&wb_dma->head);

	platform_set_drvdata(pdev, wb_dma);
	pm_runtime_enable(dev);
	PRT_TRACE_END("\n");
	PRT_INFO("probe end!\n");
	return 0;
}

static int wb_dma_remove(struct platform_device *pdev)
{
	/* struct viss_wb_dma *wb_dma = platform_get_drvdata(pdev); */


	return 0;
}

#ifdef CONFIG_PM
static int wb_dma_runtime_resume(struct device *dev)
{
	return 0;
}

static int wb_dma_runtime_suspend(struct device *dev)
{
	return 0;
}
#endif

#ifdef CONFIG_PM_SLEEP
static int wb_dma_resume(struct device *dev)
{
	return 0;
}

static int wb_dma_suspend(struct device *dev)
{
	return 0;
}
#endif /* CONFIG_PM_SLEEP */

static const struct of_device_id wb_dma_of_match[] = {
	{
		.compatible = "lombo,viss-wb-dma",
	},
};

static const struct dev_pm_ops wb_dma_pm_ops = {

#ifdef CONFIG_PM_SLEEP
	SET_SYSTEM_SLEEP_PM_OPS(wb_dma_suspend, wb_dma_resume)
#endif

#ifdef CONFIG_PM
	SET_RUNTIME_PM_OPS(wb_dma_runtime_suspend, wb_dma_runtime_resume, NULL)
#endif

};

static struct platform_driver wb_dma_driver = {
	.probe		= wb_dma_probe,
	.remove		= wb_dma_remove,
	.driver = {
		.of_match_table = wb_dma_of_match,
		.name		= WB_DMA_DRV_NAME,
		.owner		= THIS_MODULE,
		.pm		= &wb_dma_pm_ops,
	}
};

module_platform_driver(wb_dma_driver);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" WB_DMA_DRV_NAME);

